<style lang="scss">
    @import './style.scss';
</style>
<template>
    <div class="ui-tree-menu ui-nav-tree" :class="{'draggable':draggable}">
        <div class="ui-tree-menu-tool ui flex" :class="{transparent:transparent}">
            <el-tooltip class="item" effect="dark" content="展开或收起全部目录" placement="top-start">
                <el-button @click="toggleAllExpand" size="small" type="text" class="expand-button">
                    <i class="el-icon-caret-right arrow" :class="{expanded:expandAll}"></i>
                </el-button>
            </el-tooltip>
            <div class="item one">
                <slot name="tree" v-if="showSlot"></slot>
                <el-input size="small"
                          class="ui-tree-menu-filterInput"
                          v-show="!showSlot"
                          :placeholder="placeholder"
                          v-model="filterText"
                          @blur="hideInput"
                          ref="filterInput">
                    <template slot="append" v-if="$slots.tree">
                        <i class="icon times"></i>
                    </template>
                </el-input>
            </div>
            <el-tooltip v-show="showSlot" class="item" effect="dark" content="过滤" placement="top">
                <i class="filter icon f-16 m-l-10 fliter-i" @click="showFilter"></i>
            </el-tooltip>
            <el-tooltip v-if="editable && !noRootAdd" class="item" effect="dark" content="增加根节点" placement="top">
                <i class="plus square icon f-24 m-l-10 root-add" @click="rootAdd"></i>
            </el-tooltip>
        </div>
        <!-- v-on="$listeners" 把父子组件的所有on属性打通 -->
        <el-tree :props="props" v-on="$listeners"
                 v-if="isRender && myTreeData"
                 :draggable="draggable"
                 class="ui-el-tree"
                 ref="tree"
                 :data="myTreeData"
                 @node-click="nodeClick"
                 :filter-node-method="filterNode"
                 highlight-current
                 :node-key="nodeKey"
                 :expand-on-click-node="false"
                 @node-drag-start="handleDragStart"
                 @node-drag-end="handleDragEnd"
                 :default-expand-all="expandAll"
                 :allow-drop="allowDrop"
                 :default-expanded-keys="defaultExpandedKeys">
            <div class="custom-tree-node ui flex w-100p" slot-scope="{ node, data }">
                <i v-if="data.children !== undefined && data.children !== null && data.children.length > 0"
                   :class="[node.expanded ? 'folder open outline icon' : 'folder outline icon']"></i>
                <i v-else :class="node.data.icon? node.data.icon + ' icon': 'file outline icon'"></i>
                <div class="ui flex item one dis-i-b">
                    <div class="item one" v-if="$scopedSlots.default">
                        <div class="default">
                            <slot :node="node" :data="data"></slot>
                        </div>
                    </div>
                    <div class="item one" v-if="!$scopedSlots.default">
                        <div class="default" :title="node.data.label">
                            {{ node.data.label }}
                        </div>
                    </div>
                    <div class="toolbar" v-if="editable">
                        <i title="新增"
                           v-if="!node.data.un_add"
                           class="icon plus f-12"
                           @click="selectMode(node, data, 'add', () => append(node, data))"></i>
                        <i title="修改"
                           v-if="!node.data.un_edit"
                           class="icon pencil alternate f-12"
                           @click="selectMode(node, data, 'edit', () => edit(node, data))"></i>
                        <i title="删除"
                           v-if="!node.data.un_remove"
                           class="icon trash f-12"
                           @click="selectMode(node, data, 'remove', () => remove(node, data))"></i>
                    </div>
                </div>
            </div>
        </el-tree>
    </div>
</template>
<script>
    let maskDom //蒙版
    export default {
        name: 'ui-tree-menu',
        props: {
            data: {
                type: Array,
                required: true
            },
            props: {
                type: Object,
                default: function () {
                    return {
                        children: 'children',
                        label: 'label'
                    }
                }
            },
            placeholder: {
                type: String,
                default: '请输入关键字进行过滤'
            },
            'default-expand-all': {
                type: Boolean,
                default: true
            },
            'transparent': {
                type: Boolean,
                default: false
            },
            'node-key': {
                type: String,
                default: 'id'
            },
            'current-key': {},
            'default-expanded-keys': {
                type: Array
            },
            'operate-url': {
                type: String
            },
            editable: {
                type: Boolean,
                default: false
            },
            draggable: {
                type: Boolean,
                default: false
            },
            noRootAdd: {
                type: Boolean,
                default: false
            },
            'god-mode': {
                type: Function
            },
            inputMax: {
                type: Number,
                default: 50
            },
            inputMin: {
                type: Number,
                default: 0
            },
            allowDrop: {
                type: Function
            },
        },
        data() {
            return {
                showSlot: false,
                myTreeData: [], //props传入的数据是不能直接修改的 所以必须自己建立一个本作用域下的数据
                filterText: '',
                expandAll: this.defaultExpandedKeys ? false : this.defaultExpandAll,
                isRender: true,
                nodeVM: null,
                popupPosition: null,
                inputVal: null,
                rootFlag: false,
                update: {
                    type: null,
                    parentcode: null,
                    parentCode: null,
                    id: null,
                    label: null,
                    index: null
                },
                cacheTreeData: null
            }
        },
        computed: {
            expandAllButtonName() {
                return this.expandAll ? '收起' : '展开'
            },
            uiInstance() {
                return this.$refs['tree']
            }
        },
        methods: {
            // 根据条件切换上帝模式
            selectMode(node, data, type, fn) {
                if (this.godMode) return this.godMode(node, data, type)
                return fn(node, data)
            },
            showFilter() {
                this.showSlot = false
                this.$nextTick(() => {
                    this.$refs.filterInput.focus()
                })
            },
            hideInput() {
                if (this.$slots.tree) {
                    this.showSlot = true
                }
            },
            rootAdd() {
                // 如果开始上帝模式
                if (this.godMode) return this.godMode('%', this.myTreeData, 'rootAdd')

                // 定位
                let reactObj = this.$el.getBoundingClientRect()
                this.popupPosition = {
                    left: reactObj.left,
                    top: reactObj.top,
                    width: reactObj.width,
                    height: 40,
                    arrow: {
                        left: 18,
                        top: -4,
                        bottom: ''
                    }
                }
                // 修改顶层添加数据
                this.update.parentcode = '%'
                this.update.parentCode = '%'
                this.update.type = 'add'
                // 打开输入框
                this.rootFlag = true
                this.popup('model', this.myTreeData)
            },
            append(node, data) {
                setTimeout(() => {
                    // 初始化上传数据
                    this.log('点击添加:', node)
                    this.update.type = 'add'
                    this.update.parentcode = node.data.id
                    this.update.parentCode = node.data.id

                    //定位
                    this.getPosition('model')
                    // 打开输入框
                    this.popup('model', data)
                })
            },
            edit(node, data) {
                setTimeout(() => {
                    // 初始化上传数据
                    this.log('点击编辑:', node)
                    this.update.type = 'edit'
                    this.update.id = node.data.id

                    // 传递当前节点值给模版
                    this.inputVal = node.label
                    // 定位
                    this.getPosition()
                    // 打开输入框
                    this.popup('list', data)

                })
            },
            remove(node, data) {
                this.$confirm('此操作将永久删除该结点, 是否继续?', '提示', {
                    confirmButtonText: '确定',
                    cancelButtonText: '取消',
                    type: 'warning'
                }).then(() => {
                    this.cacheTreeData = JSON.parse(JSON.stringify((this.myTreeData)))
                    const parent = node.parent;
                    const children = parent.data.children || parent.data;
                    const index = children.findIndex(d => d.id === data.id);
                    children.splice(index, 1)

                    //todo: (donkey)  上传删除数据
                    this.update.type = 'remove'
                    this.update.id = node.data.id
                    this.log('删除节点:', this.update)

                    //调用后端http请求
                    this.commonRequest(data)
                    /*this.$message({
                        type: 'success',
                        message: '删除成功!'
                    })*/
                }).catch(() => {
                    this.$message({
                        type: 'info',
                        message: '已取消删除'
                    });
                });
            },
            handleDragStart() {
                this.cacheTreeData = JSON.parse(JSON.stringify(this.myTreeData))
            },
            handleDragEnd: function (draggingNode, dropNode, dropType, ev) {
                this.$emit('drag-end', dropType)
                // 如果拖动到没有到有效距离就不执行任何操作
                // console.log(dropType)
                if (dropType === 'none') return

                this.flushTree() // 重绘树

                // 如果开始上帝模式
                if (this.godMode) return this.godMode(draggingNode, dropNode, dropType)

                // this.log('tree drag end: ', dropNode && dropNode.label, dropType, ev, draggingNode, dropNode);
                this.update.type = 'drag'
                this.update.id = draggingNode.data.id
                const getIndex = (data) => { // 获取序号
                    if (!data.childNodes) return
                    for (let i = 0, len = data.childNodes.length; i < len; i++) {
                        if (data.childNodes[i].data.id === this.update.id) {
                            this.update.index = i + 1
                            break
                        }
                    }
                }

                if (dropType === 'inner') {
                    this.update.parentcode = dropNode.data.id
                    this.update.parentCode = dropNode.data.id
                    getIndex(dropNode)
                } else {
                    if (!dropNode) return // 拖动到外部静止任何保存操作
                    this.update.parentcode = dropNode.parent.data.id || '%'
                    this.update.parentCode = dropNode.parent.data.id || '%'
                    getIndex(dropNode.parent)
                }

                //todo: (donkey) 上传拖动数据
                this.log('拖动节点:', this.update)

                // 调用后端http请求
                if (!this.godMode) this.commonRequest(this.update)
            },
            getPosition(mode) {
                let reactObj = this.nodeVM.$el.getBoundingClientRect()
                let nodeCustom = $(this.nodeVM.$el).find('.custom-tree-node')[0].getBoundingClientRect()
                let nodeWidth = nodeCustom.width

                let isBottom = mode && mode === 'model' && (document.body.clientHeight - nodeCustom.top) < 90 //在添加模式下如果选项靠近窗口底部就是 true

                this.popupPosition = {
                    left: reactObj.left - 3,
                    top: isBottom ? reactObj.top - 90 : reactObj.top,
                    width: this.nodeVM.$el.clientWidth,
                    height: 34,
                    arrow: {
                        left: this.nodeVM.$el.clientWidth - nodeWidth + 3,
                        top: isBottom ? '' : -4,
                        bottom: isBottom ? -4 : ''
                    },
                    listWidth: nodeWidth - 23
                }
                // this.log(this.popupPosition);
            },
            // 弹出编辑窗口
            popup(mode, data) {

                /* ---------------- 生成maskDom -----------------*/
                // 模态框模板
                const model = `<div id="ui-tree-menu-mask">
                                <div class="modal-wrapper" style="top:${this.popupPosition.top + this.popupPosition.height + 3}px; left:${this.popupPosition.left}px; width:${this.popupPosition.width}px;">
                                    <div class="modal-wrapper-arrow" style="left:${this.popupPosition.arrow.left}px;top:${this.popupPosition.arrow.top}px;bottom:${this.popupPosition.arrow.bottom}px;"></div>
                                        <div class="ui fl-r flex w-100p">
                                         <div class="ui input mini flex-1">
                                           <i class="check circle outline icon link tianjia pos-a" style="top:5px;right:5px;"></i>
                                           <input class="w-100p p-r-20" style="border-top-right-radius: 0;border-bottom-right-radius: 0;" type="text" placeholder="请输入内容, 按回车保存...">
                                         </div>
                                         <a href="javascript:" class="ui basic label  quxiao m-0" style="border-top-left-radius: 0;border-bottom-left-radius: 0;padding: 7px 5px 0;">取消</a>
                                        </div>
                                </div>
                            </div>`

                // 列表编辑模板
                const list = `<div id="ui-tree-menu-mask">
                                <div class="list-wrapper" style="top:${this.popupPosition.top}px; left:${this.popupPosition.left}px; width:${this.popupPosition.width}px;">
                                    <div class="ui fl-r flex" style="width:${this.popupPosition.listWidth}px;">
                                     <div class="ui input mini flex-1">
                                       <i class="check circle outline icon link bianji pos-a" style="top:5px;right:5px;"></i>
                                       <input class="w-100p p-r-20" style="border-top-right-radius: 0;border-bottom-right-radius: 0;" type="text" placeholder="请输入内容, 按回车保存..." value="${this.inputVal}">
                                     </div>
                                     <a href="javascript:" class="ui basic label  quxiao m-0" style="border-top-left-radius: 0;border-bottom-left-radius: 0;padding: 7px 5px 0;">取消</a>
                                    </div>
                                </div>
                            </div>`

                // 根据输入选择的模式弹出需要的窗口
                if (mode === 'model') $('body').append(model) // 添加模式
                if (mode === 'list') $('body').append(list)   // 修改模式

                /* ---------------- end -----------------*/

                /* ---------------- maskDom 上面的事件 -----------------*/

                // 获得当前弹出dom对象
                maskDom = $('#ui-tree-menu-mask')

                // 移动光标到输入框内
                maskDom.find('input').focus()

                // 点击编辑按钮
                maskDom.find('.bianji').on('click', () => {
                    this.inputVal = maskDom.find('input').val()
                    if (this.inputVal !== '') {

                        // 验证数据
                        if (!this.vlidata()) return

                        data.label = this.inputVal
                        //todo: (donkey)  添加数据到远端
                        this.update.label = this.inputVal
                        this.log('编辑数据到远端:', this.update)
                        //调用后端http请求
                        this.commonRequest(data)
                    }
                    maskDom.remove()
                })
                // 点击添加按钮
                maskDom.find('.tianjia').on('click', () => {
                    this.inputVal = maskDom.find('input').val()
                    if (this.inputVal !== '') {
                        this.inputVal = this.inputVal.trim()
                        // 验证数据
                        if (!this.vlidata()) return

                        // 新增节点
                        const newChild = {id: '', label: this.inputVal};
                        if (this.rootFlag) {
                            data.push(newChild)
                            this.rootFlag = false
                        } else {
                            if (!data.children) {
                                this.$set(data, 'children', []);
                            }
                            data.children.push(newChild)
                        }
                        //todo: (donkey)  添加数据到远端
                        this.update.label = this.inputVal
                        this.log('添加数据到远端:', this.update)
                        //调用后端http请求
                        this.commonRequest(data)

                    }
                    maskDom.remove()
                })
                // 点击取消按钮
                maskDom.find('.quxiao').on('click', () => {
                    maskDom.remove()
                })

                // 键盘快捷操作
                const escFn = (e) => {
                    // 按 esc键
                    if (e.key === 'Escape') maskDom.remove()
                    // 按回车键
                    if (e.key === 'Enter') {
                        maskDom.find('.tianjia').trigger('click')
                        maskDom.find('.bianji').trigger('click')
                    }
                    // 只有这两个回车和退出才销毁监听
                    if (e.key === 'Escape' || e.key === 'Enter') window.removeEventListener('keydown', escFn)
                }
                // 然后重新绑定最新的事件
                window.addEventListener('keydown', escFn)

                /*--------------------- end ---------------------------*/
            },
            // 调用后端http请求
            commonRequest(nodeData) {
                this.$http.Datas(this.operateUrl, this.update).then(data => {
                    // 拖动后收到后台禁止拖动信息后还原数据
                    if (data.error_message) {
                        this.$message.error(data.error_message)
                        this.myTreeData = this.cacheTreeData
                        this.flushTree()
                        return
                    }
                    // 拖动成功
                    if (data.obj) {
                        this.myTreeData = data.obj;
                        this.onChange(data.obj)
                        this.$message.success('操作成功!')
                    } else {
                        console.log('操作失败,请在试一次!')
                    }
                })
            },
            // 刷新数据
            flushTree() {
                // 刷新整个树, 要不树上的图标无法发生改变
                const _cache = this.myTreeData
                this.myTreeData = ''
                this.$nextTick(() => {
                    this.myTreeData = _cache
                })
            },
            nodeClick(data, node, tree) {
                this.nodeVM = null
                this.nodeVM = tree
                // this.$emit('node-click', data, node, tree)
                this.$emit('update:currentKey', data[this.nodeKey])
            },
            onChange(node) {
                this.$emit('change', this.update, node)
            },
            filterNode(value, data) {
                if (!value) return true;
                if (data.label && data.label.indexOf(value) !== -1) {
                    return true
                }
                let pinyinCode = data.pinyin_code || data.pinyinCode;
                if (pinyinCode) {// 拼音检索
                    const py = pinyinCode.toLowerCase()
                    const v = value.toLowerCase()
                    if (py.indexOf(v) !== -1) {
                        return true
                    }
                }
                return false
            },
            toggleAllExpand() {//全部展开收起
                // 修改展开状态
                this.expandAll = !this.expandAll
                // 重新渲染tree,用v-if来重置tree状态
                this.isRender = false
                this.$nextTick(() => {
                    this.isRender = true
                })
            },
            highlight() {//高亮节点
                this.$nextTick(() => {
                  const Tree = this.$refs.tree
                  Tree.setCurrentKey(this.currentKey) //高亮当前节点
                })
            },
            vlidata() {
                // 验证数据长度
                if (parseInt(this.inputVal.length) > parseInt(this.inputMax)) {
                    this.$message({
                        showClose: true,
                        message: `输入的数据超过了${this.inputMax}个字符限制`,
                        type: 'error'
                    })
                    return false
                }
                if (parseInt(this.inputVal.length) < parseInt(this.inputMin)) {
                    this.$message({
                        showClose: true,
                        message: `输入的数据少于${this.inputMin}个字符限制`,
                        type: 'error'
                    });
                    return false
                }

                 if (!this.inputVal.trim()) {
                    this.$message({
                        showClose: true,
                        message: `输入不能为空`,
                        type: 'error'
                    });
                    return false
                }
                return true
            }
        },
        watch: {
            filterText(val) {
                this.$refs.tree.filter(val);
            },
            currentKey(v) {// 更新高亮
                this.highlight()
            },
            data(v) {// data改变, 更新高亮
                this.highlight();
                // console.log('watch',v)
                this.myTreeData = JSON.parse(JSON.stringify(v));
            }
        },
        mounted() {
            this.highlight()
            if (this.data) {
                this.myTreeData = JSON.parse(JSON.stringify(this.data))
            }
            this.$slots.tree ? this.showSlot = true : ''

            // console.log(this.$listeners)
        },
        beforeDestroy() {
            // 离开前销毁popup的DOM
            maskDom = $('#ui-tree-menu-mask')
            if (maskDom.length > 0) maskDom.remove()
        }
    }
</script>
